#include "rewine/LibManager.hpp"

#include "dllso/DllsoInit.hpp"
#include "infra/Shelter.hpp"
#include "misc/fs_ext.hpp"
#include "misc/string_ext.hpp"
#include "rewine/LoaderSingleton.hpp"
#include "rewine/LoggerSingleton.hpp"
#include "rewine/impl/CCustomExport.hpp"
#include "rewine/impl/CCustomImage.hpp"

#include <algorithm>
#include <utility>

using std::make_pair;
using std::make_shared;
using std::shared_ptr;
using std::string;

namespace rewine
{

LibEntry::LibEntry(const std::string& m_ExportName)
        : m_ExportName(m_ExportName)
{
    m_pPreferred.reset(g_Loader->createCustomImage(m_ExportName));
    m_pPreferred->setPinned(true);
    m_Images.push_front(m_pPreferred);

    m_pFallback.reset(g_Loader->createCustomImage(m_ExportName));
    m_pFallback->setPinned(true);
    m_Images.push_back(m_pFallback);
}

LibEntry::list_t::const_iterator
LibEntry::find(IImage *pImage) const
{
    return std::find_if(m_Images.cbegin(), m_Images.cend(), [pImage](const list_t::value_type& sp) -> bool {
        return pImage == sp.get();
    });
}

LibEntry::list_t::iterator
LibEntry::find(IImage *pImage)
{
    return std::find_if(m_Images.begin(), m_Images.end(), [pImage](const list_t::value_type& sp) -> bool {
        return pImage == sp.get();
    });
}

bool
LibEntry::addAfter(IImage *pSentry, IImage *pImage)
{
    if (contains(pImage)) {
        return false;
    }
    auto it = m_Images.cend();
    if (pSentry) {
        it = find(pSentry);
    } else if (m_pPreferred.operator bool()) {
        it = find(m_pPreferred.get());
    }
    if (it == m_Images.cend()) {
        return false;
    }
    ++it;
    m_Images.emplace(it, std::shared_ptr<IImage>(pImage));
    return true;
}

bool
LibEntry::addBefore(IImage *pSentry, IImage *pImage)
{
    if (contains(pImage)) {
        return false;
    }
    auto it = m_Images.cend();
    if (pSentry) {
        it = find(pSentry);
    } else if (m_pFallback.operator bool()) {
        it = find(m_pFallback.get());
    }
    if (it == m_Images.cend()) {
        return false;
    }
    m_Images.emplace(it, std::shared_ptr<IImage>(pImage));
    return true;
}

int
LibEntry::remove(IImage *pImage)
{
    auto it = find(pImage);
    if (it == m_Images.end()) {
        return -1;
    }
    m_Images.erase(it);
    return 0;
}

int
LibEntry::removeFailed()
{
    int count = 0;
    auto it = std::remove_if(m_Images.begin(), m_Images.end(), [&count](const list_t::value_type& sp) -> bool {
        if (sp->isFailed()) {
            count++;
            return true;
        }
        return false;
    });
    m_Images.erase(it, m_Images.end());
    return count;
}

bool
LibEntry::isEmpty() const
{
    int count = 0;
    if (m_pPreferred.operator bool() && m_pPreferred->getExportCount() == 0) {
        count++;
    }
    if (m_pFallback.operator bool() && m_pFallback->getExportCount() == 0) {
        count++;
    }
    return m_Images.size() == count;
}

bool
LibEntry::tryToEmpty()
{
    auto it = std::remove_if(m_Images.begin(), m_Images.end(), [](const list_t::value_type& sp) -> bool
    {
        return !sp->checkActivity();
    });
    m_Images.erase(it, m_Images.end());
    return isEmpty();
}

bool
LibEntry::contains(IImage *pImage) const
{
    const auto it = find(pImage);
    return it != m_Images.cend();
}

IImage *
LibEntry::getByFullname(const std::string& fullname) const
{
    const auto it = std::find_if(m_Images.cbegin(), m_Images.cend(), [&fullname](const list_t::value_type& sp) -> bool {
        return fullname == sp->fullname();
    });
    if (it == m_Images.cend()) {
        return nullptr;
    }
    return (*it).get();
}

void
LibEntry::setOverride(const std::string& symbolName, std::shared_ptr<IExport> spExport, bool bPrefer) {
    auto pImage = (bPrefer) ? m_pPreferred : m_pFallback;
    pImage->set(symbolName, std::move(spExport));
}

void
LibEntry::setOverride(const std::string& symbolName, IExport *pExport, bool bPrefer)
{
    if (!pExport) {
        unsetOverride(symbolName, bPrefer);
        return;
    }
    auto spExport = std::shared_ptr<IExport>(pExport);
    setOverride(symbolName, spExport, bPrefer);
}

void
LibEntry::setOverrideForward(const std::string& symbolName, IExport *pForwarded, bool bPrefer)
{
    auto spExport = CCustomExport::create();
    spExport->bindTo(pForwarded->getValueSynchronizerRef());
    return setOverride(symbolName, spExport, bPrefer);
}

void
LibEntry::unsetOverride(const std::string &symbolName, bool bPrefer)
{
    auto pImage = (bPrefer) ? m_pPreferred : m_pFallback;
    pImage->remove(symbolName);
}

IExport *
LibEntry::resolve(const std::string &symbolName, uint16_t hint, std::shared_ptr<IImage> *pspImage) const
{
    for (const auto& pImage : m_Images) {
        if (!pImage->isParsed() || pImage->isFailed()) {
            continue;
        }
        IExport *symbol = pImage->getExportByName(symbolName, hint);
        if (symbol) {
            if (pspImage) {
                *pspImage = pImage;
            }
            return symbol;
        }
    }
    return nullptr;
}

LibEntry *
LibManager::get(const std::string &libName0)
{
    auto libName = to_lower_string(libName0);
    auto it = m_Libs.find(libName);
    if (it == m_Libs.end()) {
        auto pEntry = std::make_shared<LibEntry>(libName);
        auto key = std::string_view(&pEntry->exportName().at(0), pEntry->exportName().size());
        it = m_Libs.emplace(std::make_pair(key, pEntry)).first;
    }
    return it->second.get();
}

bool
LibManager::tryToClean(const std::string &libName0)
{
    auto libName = to_lower_string(libName0);
    auto it = m_Libs.find(libName);
    if (it == m_Libs.end()) {
        return true;
    }
    if (it->second->tryToEmpty()) {
        m_Libs.erase(it);
        return true;
    }
    return false;
}

int
LibManager::tryToCleanAll()
{
    int count = 0;
    for (auto it = m_Libs.begin(); it != m_Libs.end(); ) {
        const auto& sp = it->second;
        if (sp->tryToEmpty()) {
            count++;
            it = m_Libs.erase(it);
        } else {
            ++it;
        }
    }
    return count;
}

LibEntry *
LibManager::getByFullname(const std::string& fullname, IImage **ppImage) const
{
    for (const auto& pair : m_Libs) {
        IImage *pImage = pair.second->getByFullname(fullname);
        if (pImage) {
            if (ppImage) {
                *ppImage = pImage;
            }
            return pair.second.get();
        }
    }
    if (ppImage) {
        *ppImage = nullptr;
    }
    return nullptr;
}

} // namespace rewine
